Skip to content

feat(companion): add optimistic updates to availability edit screens#26931

Merged
dhairyashiil merged 8 commits intomainfrom
feat/companion-auto-reload-on-save
Jan 16, 2026
Merged

feat(companion): add optimistic updates to availability edit screens#26931
dhairyashiil merged 8 commits intomainfrom
feat/companion-auto-reload-on-save

Conversation

@dhairyashiil
Copy link
Member

@dhairyashiil dhairyashiil commented Jan 16, 2026

Screen.Recording.2026-01-16.at.6.56.47.PM.mov

Fixes #26908
Fixes CAL-7077

What does this PR do?

Implements optimistic updates for availability edit screens in the companion app (iOS, Android, browser extension). This is part of a larger refactoring effort to add auto-refresh/reload after save actions.

Phase 1 - EditAvailabilityNameScreen:

  • Enhanced useUpdateSchedule hook with full optimistic update support (onMutate, onSuccess, onError)
  • Refactored EditAvailabilityNameScreen.tsx and EditAvailabilityNameScreen.ios.tsx to use the mutation hook instead of direct API calls
  • Cache is now updated immediately on save, providing instant UI feedback
  • On error, cache automatically rolls back to previous state

Phase 2 - EditAvailabilityOverrideScreen:

  • Refactored EditAvailabilityOverrideScreen.tsx and EditAvailabilityOverrideScreen.ios.tsx to use useUpdateSchedule mutation hook
  • Replaced direct CalComAPIService.updateSchedule calls with the mutation hook
  • Removed local isSaving state in favor of isPending from the mutation hook
  • Override changes now update the cache automatically via optimistic updates

Phase 3 - EditAvailabilityDayScreen:

  • Refactored EditAvailabilityDayScreen.tsx and EditAvailabilityDayScreen.ios.tsx to use useUpdateSchedule mutation hook
  • Replaced direct CalComAPIService.updateSchedule calls with the mutation hook
  • Removed local isSaving state in favor of isPending from the mutation hook
  • Day availability changes now update the cache automatically via optimistic updates

Supporting Changes (by @dhairyashiil):

  • Refactored AvailabilityDetailScreen.tsx and AvailabilityDetailScreen.ios.tsx to use useScheduleById hook instead of direct API calls
  • This ensures the detail screen reads from React Query cache, enabling cache updates to propagate correctly
  • Added RefreshControl for pull-to-refresh support
  • Replaced direct API mutations with useSetScheduleAsDefault and useDeleteSchedule hooks

Updates since last revision

Fixed stale data issue on the Working Hours page (page 2 in the edit flow):

  • Refactored edit-availability-hours.tsx and edit-availability-hours.ios.tsx route files to use useScheduleById hook
  • Previously, these routes used direct CalComAPIService.getScheduleById() calls with local useState, bypassing the React Query cache
  • Now all 3 pages in the availability edit flow (detail → working hours → day edit) read from the same cache, ensuring updates propagate correctly

Fixed Date Overrides section not clickable when empty:

  • Previously, when there were no date overrides, the section was a plain View without any press handler
  • Users couldn't navigate to the edit override page to add new overrides
  • Now the "No Overrides" section is wrapped in AppPressable with navigation handler and chevron icon
  • Follows the same pattern as other sections (Weekly Schedule, Timezone) which are always clickable

Mandatory Tasks (DO NOT REMOVE)

  • I have self-reviewed the code (A decent size PR without self-review might be rejected).
  • I have updated the developer docs in /docs if this PR makes changes that would require a documentation change. N/A - internal companion app changes only.
  • I confirm automated tests are in place that prove my fix is effective or that my feature works.

How should this be tested?

Phase 1 - Edit Name/Timezone:

  1. Open the companion app (iOS/Android/browser extension)
  2. Navigate to Availability settings
  3. Edit a schedule's name or timezone
  4. Save the changes
  5. Navigate back to the availability list
  6. Expected: List should show updated name/timezone immediately without manual refresh

Phase 2 - Edit Overrides:

  1. Navigate to a schedule's detail page
  2. Add, edit, or delete a date override
  3. Save the changes
  4. Navigate back to the detail/list page
  5. Expected: Changes should reflect immediately without manual refresh

Phase 3 - Edit Day Availability:

  1. Navigate to a schedule's detail page
  2. Tap "Edit Hours" to open the Working Hours page
  3. Tap a specific day (e.g., Monday)
  4. Toggle availability on/off or modify time slots
  5. Save and navigate back
  6. Expected: Working Hours page AND detail page should reflect changes immediately without manual refresh

Date Overrides Empty State:

  1. Navigate to a schedule that has NO date overrides
  2. Tap on the "Date Overrides" section (should show "No date overrides set")
  3. Expected: Should navigate to the edit override page where you can add new overrides

Error case: If network fails, UI should roll back to previous state and show error alert

Checklist

  • My code follows the style guidelines of this project
  • I have commented my code, particularly in hard-to-understand areas
  • I have checked if my changes generate no new warnings

Human Review Checklist

  • Verify AvailabilityDetailScreen properly reads from React Query cache via useScheduleById (critical for cache updates to propagate)
  • Verify edit-availability-hours route files properly read from React Query cache via useScheduleById
  • Verify the optimistic update logic in useUpdateSchedule handles availability field correctly via spread operator
  • Confirm the rollback in onError restores both detail and list caches correctly
  • Verify Date Overrides section is clickable when empty (shows chevron, navigates to edit page)
  • Test on iOS, Android, and browser extension
  • Note: EventTypeDetail screen will be refactored in a subsequent phase

Link to Devin run: https://app.devin.ai/sessions/0e35f5cdab8943dabb66fc182e4241fe
Requested by: Dhairyashil Shinde (@dhairyashiil)

- Refactor useUpdateSchedule hook with optimistic updates
- Update EditAvailabilityNameScreen to use mutation hook instead of direct API call
- Update EditAvailabilityNameScreen.ios.tsx with same pattern
- Cache is updated immediately on save, then synced with server
- On error, cache is rolled back to previous state
@devin-ai-integration
Copy link
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

…is empty

- Update list cache in onMutate even when detail cache doesn't exist
- Remove onSettled invalidation that was causing issues with staleTime: Infinity
- Add fallback invalidation in onSuccess when list cache doesn't exist
…ates

The previous optimistic updates implementation in EditAvailabilityNameScreen
correctly updated the React Query cache, but AvailabilityDetailScreen was
still using direct CalComAPIService.getScheduleById() calls with local
useState, completely bypassing the cache.

The disconnect:
- EditAvailabilityNameScreen → useUpdateSchedule → Updates React Query Cache ✓
- AvailabilityDetailScreen → CalComAPIService.getScheduleById → Local state ✗

This meant when a user saved changes to a schedule's name/timezone, the
cache was updated but the detail screen (and subsequently the list screen)
never saw those updates because they weren't reading from the cache.

Changes:
- Refactor AvailabilityDetailScreen.tsx to use useScheduleById hook
- Refactor AvailabilityDetailScreen.ios.tsx to use useScheduleById hook
- Replace direct API calls with React Query for cache synchronization
- Add RefreshControl for pull-to-refresh support
- Use mutation hooks (useSetScheduleAsDefault, useDeleteSchedule) for actions
- Derive availability/overrides data using useMemo from query result

Now when EditAvailabilityNameScreen updates the cache, AvailabilityDetailScreen
automatically reflects those changes because both read from the same cache.
…ion hook

- Replace direct CalComAPIService.updateSchedule calls with useUpdateSchedule hook
- Remove local isSaving state, use isPending from mutation hook instead
- Cache is now updated automatically via the mutation hook's optimistic updates
- Consistent pattern with EditAvailabilityNameScreen refactoring
@devin-ai-integration devin-ai-integration bot changed the title feat(companion): add optimistic updates to EditAvailabilityNameScreen feat(companion): add optimistic updates to availability edit screens Jan 16, 2026
- Replace direct CalComAPIService.updateSchedule calls with useUpdateSchedule hook
- Remove local isSaving state, use isPending from mutation hook instead
- Cache is now updated automatically via the mutation hook's optimistic updates
- Consistent pattern with EditAvailabilityNameScreen and EditAvailabilityOverrideScreen
…eduleById hook

The working hours page (page 2 in the flow) was showing stale data because
it used direct CalComAPIService.getScheduleById() calls with local useState,
bypassing the React Query cache.

The disconnect:
- EditAvailabilityDayScreen → useUpdateSchedule → Updates React Query Cache ✓
- edit-availability-hours route → CalComAPIService.getScheduleById → Local state ✗

This meant when a user saved changes to a day's availability, the cache was
updated but the working hours page never saw those updates because it wasn't
reading from the cache.

Changes:
- Refactor edit-availability-hours.tsx to use useScheduleById hook
- Refactor edit-availability-hours.ios.tsx to use useScheduleById hook
- Replace direct API calls with React Query for cache synchronization
- Now all 3 pages in the flow read from the same cache
Previously, when there were no date overrides, the Date Overrides section
was just a plain View without any press handler, making it impossible for
users to navigate to the edit override page to add new overrides.

This follows the same pattern as other sections (Weekly Schedule, Timezone)
which are always clickable regardless of their content state.

Changes:
- Wrap the 'No Overrides' section in AppPressable with navigation handler
- Add chevron-forward icon to indicate it's tappable
- Apply fix to both AvailabilityDetailScreen.tsx and .ios.tsx
@dhairyashiil dhairyashiil marked this pull request as ready for review January 16, 2026 13:30
@graphite-app graphite-app bot added the community Created by Linear-GitHub Sync label Jan 16, 2026
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 11 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="companion/components/screens/AvailabilityDetailScreen.ios.tsx">

<violation number="1" location="companion/components/screens/AvailabilityDetailScreen.ios.tsx:203">
P1: `useMemo` should not be used for side effects. This should be `useEffect` since calling `onActionsReady` is a side effect that notifies the parent component. The original code correctly used `useEffect` here.</violation>

<violation number="2" location="companion/components/screens/AvailabilityDetailScreen.ios.tsx:216">
P1: Side effects (`showErrorAlert` and `router.back()`) should not be called directly during render. This will execute on every render when error is truthy. Move this to a `useEffect` that depends on `error`.</violation>
</file>

<file name="companion/components/screens/AvailabilityDetailScreen.tsx">

<violation number="1" location="companion/components/screens/AvailabilityDetailScreen.tsx:203">
P1: `useMemo` should not be used for side effects. This callback invocation should use `useEffect` instead. `useMemo` is for memoizing computed values, and React may skip its execution or run it multiple times unpredictably.</violation>

<violation number="2" location="companion/components/screens/AvailabilityDetailScreen.tsx:216">
P1: Side effects (`showErrorAlert`, `router.back()`) should not be called during render. This will cause multiple alerts and navigation calls on re-renders. Move error handling to a `useEffect` that watches the `error` state.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@github-actions
Copy link
Contributor

github-actions bot commented Jan 16, 2026

Devin AI is addressing Cubic AI's review feedback

New feedback has been sent to the existing Devin session.

View Devin Session


✅ Pushed commit 0f43f76

- Change useMemo to useEffect for onActionsReady callback (side effect)
- Move error handling (showErrorAlert, router.back) to useEffect
- Keep early return for error state after useEffect hooks

Fixes Cubic AI review feedback (confidence 9/10)
@linear
Copy link

linear bot commented Jan 16, 2026

@dhairyashiil dhairyashiil merged commit ef0af95 into main Jan 16, 2026
37 checks passed
devin-ai-integration bot added a commit that referenced this pull request Jan 17, 2026
This adds a new companion-typecheck.yml workflow that runs TypeScript type
checking for the companion app. This would have caught the missing useEffect
import issue in PR #26931.

Changes:
- Add new companion-typecheck.yml workflow file
- Update pr.yml to call the new workflow when companion files change
- Add typecheck-companion to the required jobs list

Co-Authored-By: anik@cal.com <adhabal2002@gmail.com>
anikdhabal added a commit that referenced this pull request Jan 19, 2026
* ci(companion): add separate typecheck workflow to catch type errors

This adds a new companion-typecheck.yml workflow that runs TypeScript type
checking for the companion app. This would have caught the missing useEffect
import issue in PR #26931.

Changes:
- Add new companion-typecheck.yml workflow file
- Update pr.yml to call the new workflow when companion files change
- Add typecheck-companion to the required jobs list

Co-Authored-By: anik@cal.com <adhabal2002@gmail.com>

* add lint checks

* test

* remove

* update typecheck

* address review

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
anikdhabal added a commit that referenced this pull request Jan 19, 2026
* ci(companion): add separate typecheck workflow to catch type errors

This adds a new companion-typecheck.yml workflow that runs TypeScript type
checking for the companion app. This would have caught the missing useEffect
import issue in PR #26931.

Changes:
- Add new companion-typecheck.yml workflow file
- Update pr.yml to call the new workflow when companion files change
- Add typecheck-companion to the required jobs list

Co-Authored-By: anik@cal.com <adhabal2002@gmail.com>

* add lint checks

* test

* remove

* update typecheck

* address review

* chore:- hide apps with missing required keys from app store

* update

* Delete packages/app-store/_utils/hasRequiredAppKeys.test.ts

* Update validateAppKeys.ts

* Update validateAppKeys.ts

* Update validateAppKeys.test.ts

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This was referenced Jan 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community Created by Linear-GitHub Sync size/XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

iOS : Dates overrides state update

2 participants

Comments